home *** CD-ROM | disk | FTP | other *** search
/ Info-Mac 4 / Info_Mac IV CD-ROM (Pacific HiTech Inc.)(August 1994).iso / Development / Source / PopupCDEF 1.0b3 / PopupLib.c < prev    next >
Text File  |  1994-03-16  |  48KB  |  1,524 lines

  1. /* See the file Distribution for distribution terms.
  2.     (c) Copyright 1994 Ari Halberstadt */
  3.  
  4. /*    Functions implementing a popup menu. To create a popup call
  5.     PopupBegin and then PopupCurrentSet to set the initial item.
  6.     Use PopupCurrent to find out which item was selected. There
  7.     are quite a few other functions for controling how the popup
  8.     is displayed: whether the popup is drawn, whether the popup
  9.     is visible, enabling and disabling the popup, whether to
  10.     use the window font to display the popup, setting the text
  11.     style for the title, and more. The defaults are set by
  12.     PopupBegin to be those recommended by Apple in IM-VI. This
  13.     library is the basis for a popup menu 'CDEF'.
  14.     
  15.     You can use the popup CDEF to create a popup menu. The control's
  16.     contrlData field will contain a handle to the popup. If you need to
  17.     directly modify the popup, you can call the popup library routines
  18.     on the handle. However, first call PopupVersion and make sure it
  19.     is equal to kPopupVersion. If it isn't, then don't use the popup
  20.     library to modify the control. In practice, it is better to use
  21.     Control Manager routines to modify the popup.
  22.     
  23.     If you implement keyboard equivalents of menu commands, then you can
  24.     use PopupHilite to hilite the title of the menu while executing the
  25.     menu command.
  26.     
  27.     93/03/15 aih
  28.     - The current selection will display items with small icons properly.
  29.     Formerly, this would display a large icon for items that had small or
  30.     reduced icons, and wouldn't display the large icon once a different icon
  31.     had been selected. Reduced icons still aren't displayed in the current
  32.     selection, and I'm not sure why not, since I copy the command key character
  33.     for reduced icons (0x1D) to the menu item used to draw the current
  34.     selection.
  35.     - When the useWFont variation code is used, the menu will be drawn in
  36.     the font and size of the popup menu's port. Formerly, using the useWFont
  37.     variation code produced incorrect results, but I fixed this by setting the
  38.     system font and size low-memory globals instead of trying to change the
  39.     font and size of the Window Manager's port.
  40.     - The currently selected item is drawn in gray if it is disabled. Formerly,
  41.     the item was only drawn in gray if it was disabled and if the menu item
  42.     was drawn using the MDEF.
  43.     - Updated "To Do" list.
  44.     - Clicking in the popup's title will also pull down the menu. This
  45.     is consistent with the operation of the system 7 popup CDEF.
  46.     - Added support for the popupFixedWidth variation code.
  47.     
  48.     93/12/28 aih
  49.     - Continued cleaning up code, added some more comments.
  50.     - Uses menu definition function to draw the current selection. This
  51.     eliminated a couple of rectangles from the popup structure and
  52.     means that the current selection will always be drawn correctly,
  53.     even if it includes icons and command keys. A special one item menu
  54.     is used to hold a copy of the currently selected menu item. This special
  55.     menu is then used to calculate the height of the item and to draw the
  56.     item.
  57.     
  58.     93/12/26 aih
  59.     - Major overhaul. Rewrote rectangle calculation code, since it wasn't
  60.     working right. It's still not as simple as I'd like, but it's better
  61.     than before. Also added attributes to make more compatible with Apple's
  62.     popup CDEF (such as using the window font to display the menu).
  63.     
  64.     93/12/24 aih
  65.     - Converted to use handles.
  66.     - Removed dependence on all other libraries, so that this library
  67.     is completely self contained; the few external functions that were
  68.     necessary (e.g., strcpy, pstrfit) were copied or coded directly into
  69.     this file. This will make this library more suitable for use as a
  70.     CDEF since the linker won't pull in a lot of extra code from other
  71.     libraries.
  72.     
  73.     93/12/23 aih
  74.     - updated for current version of libraries
  75.     - added port parameter to PopupBegin
  76.     
  77.     91/11/11 aih
  78.     - Removed useless "PopupObj..." stuff
  79.     
  80.     91/03/01-05 aih
  81.     - Update events are handled
  82.     - Added comment describing this file
  83.     - The popup's title is allocated as a handle in the heap
  84.     - The popup is allocated as a handle in the heap
  85.     - Attribute values are only updated if nescessary
  86.     - The function PopupDraw first draws the popup to an offscreen bitmap
  87.     and then copies it to the popup's port. This eliminates flicker when
  88.     the popup is used as a CDEF, since the Control Manager sends a draw
  89.     message to a control whenever the control is clicked, even if no
  90.     drawing is actually needed.
  91.     
  92.     91/01/05 aih
  93.     - Inserted this standard header in all files
  94.  
  95.     90/12/15 Ari Halberstadt (aih)
  96.     - Created this file */
  97.  
  98. #include <Script.h>
  99. #include "PopupLib.h"
  100.  
  101. /*---------------------------------------------------------------------------*/
  102. /* constants */
  103. /*---------------------------------------------------------------------------*/
  104.  
  105. /* To support drawing of icons and other special menu items in the
  106.     current selection box, we create a special private menu containing
  107.     as its sole item a copy of the currently selected menu item. We
  108.     then call the menu definition function to draw the menu. If this
  109.     feature is disabled, only the text of the current menu item is
  110.     drawn. */
  111. #define DONT_USE_MDEF                (0)
  112.     
  113. #ifndef NDEBUG
  114.  
  115.     /* Define DRAW_RECTANGLES as 1 to enable framing of the various rectangles
  116.         of the menu. This is useful when debugging since it clearly displays the
  117.         rectangles, which otherwise can be tricky to calculate. */
  118.     #define DRAW_RECTANGLES            (0)
  119.  
  120.     /* To reduce flicker the popup is normally first drawn to an off-screen
  121.         bitmap, then copied to the screen. To make debugging easier disable
  122.         offscreen drawing. Then you can step through each draw routine to
  123.         ensure that it is drawing correctly. */
  124.     #define DONT_DRAW_OFFSCREEN    (0)
  125.     
  126.     /* Another useful debugging trick is to define the following as something
  127.         greater than 1. This helps spot and fix off-by-one errors caused by
  128.         not taking into account the size of the frame and drop shadow. 
  129.         A value of at least 1/4" (18 pixels) provides good visual feedback
  130.         and allows using a ruler to get rough measurments of areas. */
  131.     #define kFrameSize                (1)    /* width of frame around popup */
  132.     #define kShadowSize                (1)    /* width of shadow around popup */
  133.     
  134. #else /* NDEBUG */
  135.  
  136.     #define DRAW_RECTANGLES            (0)    /* if 1, draws frames around areas */
  137.     #define DONT_DRAW_OFFSCREEN    (0)    /* if 1, doesn't use offscreen bitmap */
  138.     #define kFrameSize                (1)    /* width of frame around popup */
  139.     #define kShadowSize                (1)    /* size of shadow around popup */
  140.     
  141. #endif /* NDEBUG */
  142.  
  143. #define kEllipses                        '…'    /* character appended to long strings */
  144. #define kPopupMenuID                    (1)    /* id of popup's private menu */
  145. #define kShadowOffset                (3)    /* amount to offset shadow from frame */
  146. #define kArrowWidth                    (11)    /* width of the down arrow */
  147. #define kArrowHeight                    (6)    /* height of the down arrow */
  148. #define kArrowMargin                    (5)    /* margin around arrow */
  149. #define kTitleMargin                    (5)    /* margin around title */
  150. #define kTitleMarginBottom            (1)    /* margin below title */
  151.  
  152. /* special values for the command key field of a menu item */
  153. enum {
  154.     kCmdHier = hMenuCmd,
  155.     kCmdScript,
  156.     kCmdIconReduced,
  157.     kCmdIconSmall,
  158.     kCmdReserved
  159. };
  160.  
  161. /*---------------------------------------------------------------------------*/
  162. /* low-memory globals */
  163. /*---------------------------------------------------------------------------*/
  164.  
  165. #define TopMenuItem    (0x0A0A)
  166. #define AtMenuBottom    (0x0A0C)
  167. #define SysFontFam    (0x0BA6)
  168. #define SysFontSiz    (0x0BA8)
  169.  
  170. static void LMSetTopMenuItem(short top)
  171. {
  172.     *(short *) TopMenuItem = top;
  173. }
  174.  
  175. static void LMSetAtMenuBottom(short bottom)
  176. {
  177.     *(short *) AtMenuBottom = bottom;
  178. }
  179.  
  180. static short LMGetSysFontFam(void)
  181. {
  182.     return(*(short *) SysFontFam);
  183. }
  184.  
  185. static void LMSetSysFontFam(short font)
  186. {
  187.     *(short *) SysFontFam = font;
  188. }
  189.  
  190. static short LMGetSysFontSiz(void)
  191. {
  192.     return(*(short *) SysFontSiz);
  193. }
  194.  
  195. static void LMSetSysFontSiz(short font)
  196. {
  197.     *(short *) SysFontSiz = font;
  198. }
  199.  
  200. /*---------------------------------------------------------------------------*/
  201. /* utilities, copied from various libraries to reduce code overhead in CDEF */
  202. /*---------------------------------------------------------------------------*/
  203.  
  204. #define HandleValidSize(h, n)    (true)
  205. #define MenuValid(m)                (true)
  206. #define require(x)                ((void) 0)
  207. #define ensure(x)                    ((void) 0)
  208. #define check(x)                    ((void) 0)
  209.  
  210. /* return minimum of two numbers */
  211. static long min(long a, long b)
  212. {
  213.     return(a < b ? a : b);
  214. }
  215.  
  216. /* return maximum of two numbers */
  217. static long max(long a, long b)
  218. {
  219.     return(a > b ? a : b);
  220. }
  221.  
  222. /* fit string to width by truncating it and adding the extra character,
  223.     return width of string */
  224. static short pstrfit(Str255 str, short maxwidth, char extra)
  225. {
  226.     short extrawidth;
  227.     short width;
  228.  
  229.     require(maxwidth >= 0);
  230.     width = StringWidth(str);
  231.     if (width > maxwidth) {
  232.         if (maxwidth == 0) {
  233.             /* optimization */
  234.             width = 0;
  235.             *str = 0;
  236.         }
  237.         else {
  238.             /* truncate one character (or multi-byte character)
  239.                 at a time */
  240.             while (*str > 0) {
  241.                 str[*str] = extra;
  242.                 width = StringWidth(str);
  243.                 if (width <= maxwidth)
  244.                     break;
  245.                 while (CharByte((Ptr) str, *str) > 0) {
  246.                     check(*str > 1);
  247.                     --*str;
  248.                 }
  249.                 check(*str > 0);
  250.                 --*str;
  251.             }
  252.         }
  253.     }
  254.     ensure(width <= maxwidth);
  255.     ensure(width == StringWidth(str));
  256.     return(width);
  257. }
  258.  
  259. /* get the text attributes of the current port */
  260. static void GetTextState(TextState *text)
  261. {
  262.     GrafPtr port;
  263.     
  264.     GetPort(&port);
  265.     text->font = port->txFont;
  266.     text->face = port->txFace;
  267.     text->mode = port->txMode;
  268.     text->size = port->txSize;
  269. }
  270.  
  271. /* set the text attributes of the current port */
  272. static void SetTextState(const TextState *text)
  273. {
  274.     TextFont(text->font);
  275.     TextFace(text->face);
  276.     TextMode(text->mode);
  277.     TextSize(text->size);
  278. }
  279.  
  280. /* set the text attributes of the current port to their defaults */
  281. static void TextNormal(void)
  282. {
  283.     TextFont(0);
  284.     TextFace(0);
  285.     TextSize(0);
  286.     TextMode(srcOr);
  287. }
  288.  
  289. /* Flip the rectangle 'flip' horizontally (mirror image) relative to
  290.     the rectangle 'within'. */    
  291. static void RectFlip(Rect *flip, const Rect *within)
  292. {
  293.     short offset;
  294.     
  295.     require(RectValid(flip));
  296.     require(RectValid(within));
  297.     require(within->left <= flip->left && flip->right <= within->right);
  298.     offset = (within->right - flip->right) - (flip->left - within->left);
  299.     flip->left += offset;
  300.     flip->right += offset;
  301. }
  302.  
  303. /* paint over the rectangle with the gray pattern */
  304. static void RectDisable(const Rect *r)
  305. {
  306.     PenState pen;
  307.     Pattern pat;
  308.  
  309.     require(RectValid(r));
  310.     GetIndPattern(pat, sysPatListID, 4);
  311.     GetPenState(&pen);
  312.     PenPat(pat);
  313.     PenMode(patBic);
  314.     PaintRect(r);
  315.     SetPenState(&pen);
  316. }
  317.  
  318. /* true if the menu item is enabled */
  319. static Boolean MenuItemEnabled(MenuHandle menu, short item)
  320. {
  321.     require(MenuValid(menu));
  322.     require(0 <= item && item <= CountMItems(menu));
  323.     return(item > 31 || ((**menu).enableFlags & (1 << item)) != 0);
  324. }
  325.  
  326. /*---------------------------------------------------------------------------*/
  327. /* routines for executing a menu definition procedure */
  328. /*---------------------------------------------------------------------------*/
  329.  
  330. typedef pascal void (*MenuProcPtr)(short msg, MenuHandle menu, Rect *bounds,
  331.     Point hit, short *which);
  332.  
  333. static void mdef(short msg, MenuHandle menu, Rect *bounds,
  334.     Point hit, short *which)
  335. {
  336.     Handle mdef = (**menu).menuProc;
  337.     SignedByte state = HGetState(mdef);
  338.     HLock(mdef);
  339.     ((MenuProcPtr) *mdef)(msg, menu, bounds, hit, which);
  340.     HSetState(mdef, state);
  341. }
  342.  
  343. static void mdef_draw(MenuHandle menu, const Rect *bounds)
  344. {
  345.     Point hit = { 0, 0 };
  346.     short which = 0;
  347.     Rect tmp = *bounds;
  348.     
  349.     mdef(mDrawMsg, menu, &tmp, hit, &which);
  350. }
  351.  
  352. /*---------------------------------------------------------------------------*/
  353. /* popup menu routines */
  354. /*---------------------------------------------------------------------------*/
  355.  
  356. /* true if the popup is valid */
  357. Boolean PopupValid(PopupHandle popup)
  358. {
  359.     if (! HandleValidSize(popup, sizeof(PopupType))) return(false);
  360.     if (! MenuValid((**popup).menu)) return(false);
  361.     if ((**popup).version != kPopupVersion) return(false);
  362.     return(true);
  363. }
  364.  
  365. /*---------------------------------------------------------------------------*/
  366. /* port setup and restore */
  367. /*---------------------------------------------------------------------------*/
  368.     
  369. /* The menu definition function gets the font size in which to draw the
  370.     popup menu from the font size of the window manager port. So, to
  371.     set the system font, in addition to setting a low-memory global,
  372.     we also have to set the font size for the window manager port. */
  373. static void SetSysFontSize(short size)
  374. {
  375.     GrafPtr svport;
  376.     GrafPtr wmgrPort;
  377.     
  378.     GetPort(&svport);
  379.     GetWMgrPort(&wmgrPort);
  380.     SetPort(wmgrPort);
  381.     TextSize(size);
  382.     SetPort(svport);
  383. }
  384.  
  385. /* Remember the current drawing environment and set the drawing environment 
  386.     to that needed by the popup menu. Must be balanced with a call
  387.     to PopupPortRestore. */
  388. static void PopupPortSetup(PopupHandle popup)
  389. {
  390.     GrafPtr port;
  391.     PenState pen;
  392.     TextState text;
  393.     
  394.     require(! (**popup).state.envset);    
  395.     
  396.     /* remember current port */
  397.     GetPort(&port);
  398.     (**popup).state.oldenv.port = port;
  399.  
  400.     /* save settings for popup's port */
  401.     SetPort((**popup).port);
  402.     GetPenState(&pen);
  403.     GetTextState(&text);
  404.     (**popup).state.oldenv.pen = pen;
  405.     (**popup).state.oldenv.text = text;
  406.     
  407.     /* reset text settings to default system settings */
  408.     TextNormal();
  409.     
  410.     /* if using the window's font, use font and font size of popup's port */
  411.     if ((**popup).attr.wfont) {
  412.         TextFont(text.font);
  413.         TextSize(text.size);
  414.     }
  415.     
  416.     /* If using the window's font, then set the system font and size
  417.         to the font and size of the popup's port. */
  418.     if ((**popup).attr.wfont) {
  419.         (**popup).state.oldenv.sysfont = LMGetSysFontFam();
  420.         (**popup).state.oldenv.syssize = LMGetSysFontSiz();
  421.         LMSetSysFontFam((**popup).state.oldenv.text.font);
  422.         LMSetSysFontSiz((**popup).state.oldenv.text.size);
  423.         SetSysFontSize((**popup).state.oldenv.text.size);
  424.     }
  425.     
  426.     /* save clip region and clip to the popup's maximum bounding rectangle */
  427.     if ((**popup).state.oldenv.clip) {
  428.         Rect maxbounds = (**popup).r.maxbounds;
  429.         GetClip((**popup).state.oldenv.clip);
  430.         ClipRect(&maxbounds);
  431.     }
  432.         
  433.     (**popup).state.envset = true;
  434.     ensure((**popup).state.envset);
  435. }
  436.  
  437. /* Restore the drawing environment to its original state. Must be
  438.     balanced with a call to PopupPortSetup. */
  439. static void PopupPortRestore(PopupHandle popup)
  440. {
  441.     GrafPtr port;
  442.     PenState pen;
  443.     TextState text;
  444.     GrafPtr wmgrPort;
  445.     TextState wmgrText;
  446.  
  447.     require((**popup).state.envset);
  448.     port = (**popup).state.oldenv.port;
  449.     pen = (**popup).state.oldenv.pen;
  450.     text = (**popup).state.oldenv.text;
  451.     if ((**popup).attr.wfont) {
  452.         LMSetSysFontFam((**popup).state.oldenv.sysfont);
  453.         LMSetSysFontSiz((**popup).state.oldenv.syssize);    
  454.         SetSysFontSize((**popup).state.oldenv.syssize);
  455.     }
  456.     if ((**popup).state.oldenv.clip) {
  457.         SetClip((**popup).state.oldenv.clip);
  458.         SetEmptyRgn((**popup).state.oldenv.clip);
  459.     }
  460.     SetTextState(&text);
  461.     SetPenState(&pen);
  462.     SetPort(port);    
  463.     (**popup).state.envset = false;    
  464.     ensure(! (**popup).state.envset);
  465. }
  466.  
  467. /*---------------------------------------------------------------------------*/
  468. /* rectangle calculation routines */
  469. /*---------------------------------------------------------------------------*/
  470.  
  471. /* copy the current item from the main menu to a private menu; we can
  472.     then use the private menu to determine the size of the menu item
  473.     and to draw the menu item */
  474. static void PopupAdjustMenu(PopupHandle popup)
  475. {
  476.     const StringPtr notempty = (StringPtr) "\p ";
  477.     MenuHandle menu;
  478.     Str255 item;
  479.     Style style;
  480.     short icon;
  481.     short cmd;
  482.     
  483.     if (! (**popup).state.selection) {
  484.         /* create the private menu */
  485.         check(*notempty);
  486.         menu = NewMenu(kPopupMenuID, notempty);
  487.         if (menu) {
  488.             (**popup).state.selection = menu;
  489.             AppendMenu(menu, notempty);
  490.         }
  491.     }
  492.     if ((**popup).state.selection) {
  493.         /* Copy the current selection's attributes, except for the item's
  494.             mark, which doesn't need to be displayed. The command key is
  495.             only copied if it specifies a special icon or other feature
  496.             of the menu item. */
  497.         GetItem((**popup).menu, (**popup).state.current, item);
  498.         GetItemCmd((**popup).menu, (**popup).state.current, &cmd);
  499.         GetItemIcon((**popup).menu, (**popup).state.current, &icon);
  500.         GetItemStyle((**popup).menu, (**popup).state.current, &style);
  501.         if (*item) { /* zero length item string is not allowed in menus */
  502.             SetItem((**popup).state.selection, 1, item);
  503.             SetItemIcon((**popup).state.selection, 1, icon);
  504.             SetItemStyle((**popup).state.selection, 1, style);
  505.             if (cmd == 0 ||
  506.                  cmd == kCmdScript ||
  507.                  cmd == kCmdIconReduced ||
  508.                  cmd == kCmdIconSmall)
  509.             {
  510.                 /* The command key values 0x1B through 0x1F are reserved for use
  511.                     by Apple. The command key value 0x1B indicates a heirarchical
  512.                     menu item that has a triangle to its right, which we are not
  513.                     interested in displaying in the current selection. The other
  514.                     command key characters indicate things like small or reduced
  515.                     icons, which we do want to display in the current selection.
  516.                     A command key value of 0 indicates the lack of any command
  517.                     key. */
  518.                 SetItemCmd((**popup).state.selection, 1, cmd);
  519.             }
  520.             if (MenuItemEnabled((**popup).menu, (**popup).state.current))
  521.                 EnableItem((**popup).state.selection, 1);
  522.             else
  523.                 DisableItem((**popup).state.selection, 1);
  524.         }
  525.     }
  526.     ensure(! (**popup).state.selection ||
  527.                 CountMItems((**popup).state.selection) == 1);
  528. }
  529.  
  530. /* set popup's rectangles and erase old popup if the frame has changed */
  531. static PopupRectanglesSet(PopupHandle popup, const PopupRectanglesType *r)
  532. {
  533.     Rect bounds;
  534.     RgnHandle eraseRgn;
  535.     
  536.     bounds = (**popup).r.bounds;
  537.     if (! EqualRect(&bounds, &r->bounds)) {
  538.         eraseRgn = NewRgn();
  539.         if (eraseRgn && (**popup).utilRgn) {
  540.             /* to reduce flicker only erase the
  541.                 area that doesn't overlap */
  542.             check(EmptyRgn((**popup).utilRgn));
  543.             RectRgn(eraseRgn, &bounds);
  544.             RectRgn((**popup).utilRgn, &r->bounds);
  545.             DiffRgn(eraseRgn, (**popup).utilRgn, eraseRgn);
  546.             EraseRgn(eraseRgn);
  547.             DisposeRgn(eraseRgn);
  548.             SetEmptyRgn((**popup).utilRgn);
  549.         }
  550.         else
  551.             EraseRect(&bounds);
  552.     }
  553.     (**popup).r = *r;
  554. }
  555.  
  556. /* calculate the popup's rectangles */
  557. void PopupCalculate(PopupHandle popup)
  558. {
  559.     struct {                                /* margins around some items */
  560.         struct { short left, right; } selection;
  561.         struct { short left, right; } title;
  562.         struct { short left, right; } arrow;
  563.     } margin = {
  564.         { kFrameSize, 0 },            /* selection */
  565.         { kTitleMargin, kTitleMargin },    /* title */
  566.         { 0, kArrowMargin },            /* arrow */
  567.     };
  568.     struct {                                /* minimum widths of each item */
  569.         short content;
  570.         short hilite;
  571.         short title;
  572.         short arrow;
  573.         short selection;
  574.     } minwidth;
  575.     short lineHeight;                    /* height of a line */
  576.     short menuHeight;                    /* height of the selected menu item */
  577.     short menuWidth;                    /* width of the menu */
  578.     short maxwidth;                    /* for calculating maximum widths of items */
  579.     short width;                        /* for calculating widths of items */
  580.     FontInfo font;                        /* info about the current font size */
  581.     Str255 title;                        /* the popup's title */
  582.     Rect content;                        /* rectangle enclosing content (excludes frame) */
  583.     PopupRectanglesType r;            /* the calculated rectangles */
  584.     
  585.     /* don't calculate if drawing is turned off */
  586.     if (! (**popup).attr.draw) return;
  587.     
  588.     /* initialize settings */
  589.     PopupPortSetup(popup);
  590.     PopupTitle(popup, title);
  591.  
  592.     /* The following calls to CalcMenuSize and GetFontInfo have a bizzare
  593.         requirement. When using a window font other than the system font
  594.         (i.e., using the popupUseWFont variation code) the first call to
  595.         CalcMenuSize would calculate the menu's size using the standard
  596.         system font, instead of the font that the CDEF specified. The
  597.         second call to CalcMenuSize calculates the menu's size using
  598.         the correct font size. Similarly, if GetFontInfo were called before
  599.         CalcMenuSize were called, GetFontInfo would return information about
  600.         the wrong font. This seems either like a bug in the OS (both systems
  601.         6.0.5 and 7.0), or an attempt by Apple to work around some other
  602.         bug in the OS. It took me a long time to figure out a work-around
  603.         for this problem. */
  604.         
  605.     /* get width of menu, including the width of the down arrow */
  606.     CalcMenuSize((**popup).menu);
  607.     CalcMenuSize((**popup).menu);
  608.     menuWidth = (**(**popup).menu).menuWidth + kArrowWidth + kArrowMargin;
  609.  
  610.     /* get information about the current font */
  611.     GetFontInfo(&font);
  612.     lineHeight = font.ascent + font.descent + font.leading;
  613.     
  614.     /* calculate height of current selection */
  615.     menuHeight = 0;
  616.     PopupAdjustMenu(popup);
  617.     if ((**popup).state.selection) {
  618.         CalcMenuSize((**popup).state.selection);
  619.         menuHeight = (**(**popup).state.selection).menuHeight;
  620.     }
  621.     /* the height must be at least as large as a minimum height */
  622.     if (menuHeight < lineHeight)
  623.         menuHeight = lineHeight;
  624.     if (menuHeight < kArrowHeight + 4)
  625.         menuHeight = kArrowHeight + 4;
  626.         
  627.     /* adjust margins */
  628.     
  629.     if (! *title || (**popup).attr.typein) {
  630.         /* Type-in popups don't display the title or the current selection,
  631.             so their margins aren't needed. If the title is empty then the
  632.             title's margins can also be empty. */
  633.         if ((**popup).attr.typein) {
  634.             margin.arrow.left = 2;
  635.             margin.arrow.right = 2;
  636.             menuHeight = lineHeight;
  637.             font.widMax = 0;
  638.         }
  639.         margin.title.left = 0;
  640.         margin.title.right = 0;
  641.     }
  642.     
  643.     /* for right justified popups we need a little extra space for the drop
  644.         shadow between the current selection area and the title */
  645.     if ((**popup).attr.just == teFlushRight)
  646.         margin.selection.left += kShadowSize;
  647.         
  648.     /* calculate minimum widths */
  649.     
  650.     minwidth.arrow = kArrowWidth;
  651.     minwidth.title = (*title ? font.widMax : 0);
  652.     minwidth.hilite = minwidth.title + margin.title.left + margin.title.right;
  653.     minwidth.selection = font.widMax + minwidth.arrow + margin.arrow.left + margin.arrow.right;
  654.     minwidth.content = minwidth.hilite + minwidth.selection +
  655.                             margin.selection.left + margin.selection.right;
  656.     
  657.     /* calculate rectangles */
  658.     
  659.     /* copy maxbounds rectangle */
  660.     r.maxbounds = (**popup).r.maxbounds;
  661.     
  662.     /* calculate content rectangle; all the other rectangles will be
  663.         calculated relative to this rectangle */
  664.     content.top = r.maxbounds.top + kFrameSize;
  665.     content.left = r.maxbounds.left + kFrameSize;
  666.     content.bottom = content.top + menuHeight;
  667.     content.right = content.left +
  668.         max(r.maxbounds.right - r.maxbounds.left - 2 * kFrameSize - kShadowSize,
  669.              minwidth.content);
  670.  
  671.     /* vertically center content in maxbounds */
  672.     {    short offset = ((r.maxbounds.bottom - r.maxbounds.top) -
  673.                                (content.bottom - content.top)) / 2;
  674.         OffsetRect(&content, 0, max(0, offset - kShadowSize));
  675.     }
  676.     
  677.     /* calculate hilite rectangle */
  678.     r.hilite = content;
  679.     r.hilite.left = content.left;
  680.     r.hilite.right = content.right - minwidth.selection;
  681.  
  682.     /* calculate title rectangle */
  683.     check(content.bottom - content.top >= lineHeight);
  684.     r.title.top = content.top + (content.bottom - content.top - lineHeight) / 2;
  685.     r.title.left = content.left + margin.title.left;
  686.     r.title.bottom = r.title.top + lineHeight - kTitleMarginBottom;
  687.     /* calculate width of title rectangle */
  688.     if ((**popup).attr.typein || ! *title)
  689.         width = 0; /* don't show title */
  690.     else {
  691.         /* popup has a title */
  692.         maxwidth =
  693.             r.hilite.right - r.hilite.left -
  694.             margin.title.left - margin.title.right;
  695.         check(maxwidth >= minwidth.title);
  696.         if ((**popup).attr.title.width) {
  697.             /* use fixed title width as specified by application */
  698.             width = min(maxwidth, (**popup).attr.title.width);
  699.             width = max(width, minwidth.title);
  700.         }
  701.         else {
  702.             /* use actual width of title string */
  703.             Style style = (**popup).port->txFace;
  704.             TextFace((**popup).attr.title.style);
  705.             width = pstrfit(title, maxwidth, kEllipses);
  706.             TextFace(style);
  707.         }
  708.         check(width <= maxwidth);
  709.     }
  710.     check(width >= minwidth.title);
  711.     r.title.right = r.title.left + width;
  712.  
  713.     /* adjust right edge of hilite rectangle now that width of title is known */
  714.     r.hilite.right = r.title.right + margin.title.right;
  715.     
  716.     /* calculate selection rectangle, initially using the maximum possible width */
  717.     r.selection = content;
  718.     r.selection.left = r.hilite.right + margin.selection.left;
  719.     r.selection.right = content.right - margin.selection.right;
  720.  
  721.     /* calculate width of current selection */
  722.     if ((**popup).attr.typein)
  723.         width = minwidth.selection;
  724.     else if ((**popup).attr.fixedwidth)
  725.         width = max(r.selection.right - r.selection.left, minwidth.selection);
  726.     else {
  727.         maxwidth = r.selection.right - r.selection.left;
  728.         check(maxwidth >= minwidth.selection);
  729.         width = min(maxwidth, menuWidth);
  730.         width = max(width, minwidth.selection);
  731.     }
  732.  
  733.     /* adjust right edge now that we know how wide everything is */
  734.     r.selection.right = r.selection.left + width;
  735.     content.right = r.selection.right + margin.selection.right;
  736.     
  737.     /* calculate arrow rectangle--center arrow vertically at right edge of
  738.         selection rectangle */
  739.     check(content.bottom - content.top >= kArrowHeight);
  740.     r.arrow.top = content.top + (content.bottom - content.top - kArrowHeight) / 2;
  741.     r.arrow.left = r.selection.right - kArrowWidth - margin.arrow.right;
  742.     r.arrow.bottom = r.arrow.top + kArrowHeight;
  743.     r.arrow.right = r.arrow.left + kArrowWidth;
  744.  
  745.     /* calculate frame rectangle--surrounds content area, and includes the
  746.         frame and shadow */
  747.     r.bounds.top = content.top - kFrameSize;
  748.     r.bounds.left = content.left - kFrameSize;
  749.     r.bounds.right = content.right + kFrameSize;
  750.     r.bounds.bottom = content.bottom + kFrameSize + kShadowSize;
  751.     if ((**popup).attr.just != teFlushRight)
  752.         r.bounds.right += kShadowSize; /* in teFlushRight shadow is part of content */
  753.     
  754.     /* mirror image rectangles if using a right justified menu */
  755.     if ((**popup).attr.just == teFlushRight) {
  756.         Rect maxbounds;
  757.         
  758.         /* copy and adjust maxbounds so flipping will work */
  759.         maxbounds.top = r.maxbounds.top;
  760.         maxbounds.left = r.maxbounds.left;
  761.         maxbounds.bottom = r.maxbounds.top +
  762.             max(r.maxbounds.bottom - r.maxbounds.top,
  763.                  r.bounds.bottom - r.bounds.top);
  764.         maxbounds.right = r.maxbounds.left +
  765.             max(r.maxbounds.right - r.maxbounds.left,
  766.                  r.bounds.right - r.bounds.left);
  767.  
  768.         /* flip the rectangles */
  769.         RectFlip(&r.bounds, &maxbounds);
  770.         RectFlip(&r.hilite, &maxbounds);
  771.         RectFlip(&r.selection, &maxbounds);
  772.         RectFlip(&r.title, &maxbounds);
  773.         RectFlip(&r.arrow, &maxbounds);
  774.     }
  775.     
  776.     /* make sure everything's ok */
  777.     check(RectWidth(&r.hilite) >= minwidth.hilite);
  778.     check(RectWidth(&r.title) >= minwidth.title);
  779.     check(RectWidth(&r.selection) >= minwidth.selection);
  780.     check(RectWidth(&r.arrow) >= minwidth.arrow);
  781.     check(RectWithin(&r.hilite, &r.bounds));
  782.     check(RectWithin(&r.title, &r.hilite));
  783.     check(RectWithin(&r.selection, &r.bounds));
  784.     check(RectWithin(&r.arrow, &r.selection));
  785.     check(! RectWithin(&r.hilite, &r.selection));
  786.     check(! RectWithin(&r.selection, &r.hilite));
  787.  
  788.     /* set the popup's rectangles and restore the environment */
  789.     PopupRectanglesSet(popup, &r);
  790.     PopupPortRestore(popup);
  791. }
  792.  
  793. /*---------------------------------------------------------------------------*/
  794. /* more drawing routines */
  795. /*---------------------------------------------------------------------------*/
  796.  
  797. /* draw the frame around the popup menu */
  798. static void PopupDrawFrame(PopupHandle popup)
  799. {
  800.     Rect frame;
  801.     PenState pen;
  802.     
  803.     require((**popup).state.envset);
  804.     
  805.     /* save state */
  806.     GetPenState(&pen);
  807.     
  808.     /* draw frame */
  809.     frame = (**popup).r.bounds;    
  810.     if ((**popup).attr.just == teFlushRight)
  811.         frame.right = (**popup).r.selection.right + kFrameSize + kShadowSize;
  812.     else
  813.         frame.left = (**popup).r.selection.left - kFrameSize;
  814.     frame.right -= kShadowSize;
  815.     frame.bottom -= kShadowSize;
  816.     PenSize(kFrameSize, kFrameSize);
  817.     FrameRect(&frame);
  818.  
  819.     /* draw drop shadow */
  820.     PenSize(kShadowSize, kShadowSize);
  821.     MoveTo(frame.right, frame.top + kShadowOffset);
  822.     LineTo(frame.right, frame.bottom);
  823.     LineTo(frame.left + kShadowOffset, frame.bottom);
  824.     
  825.     /* restore state */
  826.     SetPenState(&pen);
  827. }
  828.  
  829. /* draw the down arrow */
  830. static void PopupDrawArrow(PopupHandle popup)
  831. {
  832.     Rect arrow;        /* arrow's rectangle */
  833.     short    height;    /* height of arrow */
  834.     short    width;    /* width of current line */
  835.     short    i;            /* index to lines of arrow */
  836.     
  837.     require((**popup).state.envset);
  838.     arrow = (**popup).r.arrow;
  839.     height = kArrowHeight;
  840.     width = kArrowWidth - 1;
  841.     for (i = 0; i < height; i++) {
  842.         MoveTo(arrow.left + i, arrow.top + i);
  843.         LineTo(arrow.left + i + width, arrow.top + i);
  844.         width -= 2;
  845.     }
  846. }
  847.  
  848. /* draw string within bounds; if too long truncate and append ellipses */
  849. static void PopupDrawString(PopupHandle popup, Str255 str, const Rect *bounds)
  850. {
  851.     FontInfo    font;
  852.     short width;
  853.     
  854.     require((**popup).state.envset);
  855.     require(RectValid(bounds));
  856.     GetFontInfo(&font);
  857.     width = pstrfit(str, bounds->right - bounds->left, kEllipses);
  858.     if ((**popup).attr.just == teFlushRight)
  859.         MoveTo(bounds->right - width, bounds->bottom - font.descent);
  860.     else
  861.         MoveTo(bounds->left, bounds->bottom - font.descent);
  862.     DrawString(str);
  863. }
  864.  
  865. /* Draw the current selection string within the specified rectangle
  866.     by calling the menu definition function. When the menu definition
  867.     function is used to draw the current selection, any icons or other
  868.     attributes associated with the menu item are also drawn. */
  869. static void PopupDrawSelectionMDEF(PopupHandle popup, const Rect *bounds)
  870. {    
  871.     require((**popup).state.envset);
  872.     require(RectValid(bounds));
  873.  
  874.     /* set globals that determine position of menu */
  875.     LMSetTopMenuItem(bounds->top);
  876.     LMSetAtMenuBottom(bounds->bottom);
  877.     
  878.     /* draw the current selection using the menu definition function */
  879.     mdef_draw((**popup).state.selection, bounds);
  880. }
  881.  
  882. /* Draw the current selection string within the specified rectangle
  883.     without calling the menu definition function. This will only draw
  884.     the text of the current selection, not any associated icons
  885.     or other attributes, though the text attributes will be set
  886.     to match the text attributes of the menu item. */
  887. static void PopupDrawSelectionString(PopupHandle popup, const Rect *bounds)
  888. {
  889.     Str255 item;        /* current selection string */
  890.     FontInfo font;        /* information about the current font */
  891.     Style itemStyle;    /* menu item's style */
  892.     Style portStyle;    /* port's style */
  893.     Rect selection;    /* rectangle to draw string in */
  894.     
  895.     require((**popup).state.envset);
  896.     
  897.     /* allign the string with the title string and with the string
  898.         in the menu item */
  899.     GetFontInfo(&font);
  900.     selection.left = bounds->left;
  901.     selection.right = bounds->right;
  902.     selection.top = (**popup).r.title.top;
  903.     selection.bottom = (**popup).r.title.bottom;
  904.     if ((**popup).attr.just == teFlushRight)
  905.         selection.right -= font.widMax;
  906.     else
  907.         selection.left += font.widMax;
  908.     
  909.     /* use the text style of the menu item and draw the item */
  910.     GetItem((**popup).menu, (**popup).state.current, item);
  911.     GetItemStyle((**popup).menu, (**popup).state.current, &itemStyle);
  912.     portStyle = (**popup).port->txFace;
  913.     TextFace(itemStyle);
  914.     PopupDrawString(popup, item, &selection);
  915.     TextFace(portStyle);
  916.     
  917.     /* gray over selection if item is disabled */
  918.     if (! MenuItemEnabled((**popup).menu, (**popup).state.current))
  919.         RectDisable(&selection);
  920. }
  921.  
  922. /* draw the current selection by calling the menu definition function */
  923. static void PopupDrawSelection(PopupHandle popup)
  924. {
  925.     Rect selection;                /* current selection rectangle */
  926.     Boolean usemdef = false;    /* if true, selection is drawn using MDEF */
  927.  
  928.     require((**popup).state.envset);
  929.     require(! (**popup).attr.typein);
  930.     
  931.     /* exclude arrow from selection rectangle */
  932.     selection = (**popup).r.selection;
  933.     if ((**popup).attr.just == teFlushRight)
  934.         selection.left += kArrowWidth + kArrowMargin;
  935.     else
  936.         selection.right -= kArrowWidth + kArrowMargin;
  937.     check(RectValid(&selection));
  938.     
  939.     if ((**popup).state.selection) {
  940.         /* The menu definition function doesn't respect the right edge of the
  941.             bounding rectangle. If the menu is wider than the bounding rectangle,
  942.             it is simply drawn over whatever happens to be beyond the rectangle's
  943.             right edge. Since this wouldn't look very nice, we only use the
  944.             menu definition function if the menu would fit in the current
  945.             selection rectangle. Merely setting the clip region to the selection
  946.             rectangle wouldn't be sufficient, since long strings should be
  947.             truncated with an ellipses character. */
  948.         if ((**(**popup).state.selection).menuWidth <=
  949.               selection.right - selection.left)
  950.         {
  951.             #if ! DONT_USE_MDEF
  952.                 usemdef = true;
  953.             #endif /* DONT_USE_MDEF */
  954.         }
  955.     }
  956.     
  957.     /* draw current selection */
  958.     if (usemdef)
  959.         PopupDrawSelectionMDEF(popup, &selection);
  960.     else
  961.         PopupDrawSelectionString(popup, &selection);
  962. }
  963.  
  964. /* draw the title */
  965. static void PopupDrawTitle(PopupHandle popup)
  966. {
  967.     Style style;
  968.     Str255 title;
  969.     Rect rtitle;
  970.     
  971.     require((**popup).state.envset);
  972.     require(! (**popup).attr.typein);
  973.     rtitle = (**popup).r.title;
  974.     style = (**popup).port->txFace;
  975.     TextFace((**popup).attr.title.style);
  976.     PopupTitle(popup, title);
  977.     PopupDrawString(popup, title, &rtitle);
  978.     TextFace(style);
  979. }
  980.  
  981. /* gray out menu if it's disabled */
  982. static void PopupDrawEnable(PopupHandle popup)
  983. {
  984.     require(PopupValid(popup));
  985.     require((**popup).state.envset);
  986.     if (! (**popup).attr.enabled) {
  987.         Rect bounds = (**popup).r.bounds;
  988.         RectDisable(&bounds);
  989.     }
  990. }
  991.  
  992. /* erase the popup menu */
  993. static void PopupErase(PopupHandle popup)
  994. {
  995.     Rect bounds;
  996.     
  997.     require((**popup).state.envset);
  998.     bounds = (**popup).r.bounds;
  999.     EraseRect(&bounds);
  1000. }
  1001.  
  1002. #ifdef DRAW_RECTANGLES
  1003. /* draw frames around the parts of the popup */
  1004. static void PopupDrawRectangles(PopupHandle popup)
  1005. {
  1006.     Rect shadow;
  1007.     PenState pen;
  1008.     Rect frame, r;
  1009.  
  1010.     GetIndPattern(black, sysPatListID, 1);
  1011.     GetIndPattern(dkGray, sysPatListID, 2);
  1012.     GetIndPattern(gray, sysPatListID, 4);
  1013.     GetIndPattern(white, sysPatListID, 20);
  1014.  
  1015.     GetPenState(&pen);
  1016.     
  1017.     /* draw grayed outline of frame and shadow */
  1018.     if (kFrameSize > 2 || kShadowSize > 2) {
  1019.         /* calculate frame */
  1020.         frame = (**popup).r.bounds;    
  1021.         if ((**popup).attr.just == teFlushRight)
  1022.             frame.right = (**popup).r.selection.right + kFrameSize + kShadowSize;
  1023.         else
  1024.             frame.left = (**popup).r.selection.left - kFrameSize;
  1025.         frame.right -= kShadowSize;
  1026.         frame.bottom -= kShadowSize;
  1027.         
  1028.         /* draw outline of frame */
  1029.         PenPat(gray);
  1030.         FrameRect(&frame);
  1031.         InsetRect(&frame, kFrameSize, kFrameSize);
  1032.         FrameRect(&frame);
  1033.         InsetRect(&frame, -kFrameSize, -kFrameSize);
  1034.         
  1035.         /* draw outline of shadow */
  1036.         shadow.top = frame.bottom;
  1037.         shadow.left = frame.left + kShadowOffset;
  1038.         shadow.bottom = shadow.top + kShadowSize;
  1039.         shadow.right = frame.right + kShadowSize;
  1040.         FrameRect(&shadow);
  1041.         shadow.top = frame.top + kShadowOffset;
  1042.         shadow.left = frame.right;
  1043.         shadow.bottom = frame.bottom + kShadowSize;
  1044.         shadow.right = shadow.left + kShadowSize;
  1045.         FrameRect(&shadow);
  1046.         PenNormal();
  1047.     }
  1048.  
  1049.     /* draw the other rectangles */
  1050.     r = (**popup).r.maxbounds; FrameRect(&r);
  1051.     r = (**popup).r.title; FrameRect(&r);
  1052.     r = (**popup).r.hilite; FrameRect(&r);
  1053.     r = (**popup).r.selection; FrameRect(&r);
  1054.     r = (**popup).r.arrow; FrameRect(&r);
  1055.     SetPenState(&pen);
  1056. }
  1057. #endif /* DRAW_RECTANGLES */
  1058.  
  1059. /* draw the menu */
  1060. static void PopupDrawDirect(PopupHandle popup)
  1061. {
  1062.     require(PopupValid(popup));
  1063.     PopupErase(popup);
  1064.     if ((**popup).attr.visible) {
  1065.     
  1066.         /* draw all the parts of the popup */
  1067.         if (! (**popup).attr.typein) {
  1068.             /* The title should be drawn before the selection is drawn since
  1069.                 the mdef may change the port's text font, which would cause
  1070.                 the title to be drawn in the system font even if the popup
  1071.                 should use the window's font. */
  1072.             PopupDrawTitle(popup);
  1073.             PopupDrawSelection(popup);
  1074.         }
  1075.         PopupDrawFrame(popup);
  1076.         PopupDrawArrow(popup);
  1077.         PopupDrawEnable(popup);
  1078.         
  1079.         #if DRAW_RECTANGLES
  1080.             PopupDrawRectangles(popup);
  1081.         #endif /* DRAW_RECTANGLES */
  1082.     }
  1083. }
  1084.  
  1085. /* draw the popup to an offscreen bitmap, then copy the bitmap to the screen */
  1086. void PopupDraw(PopupHandle popup)
  1087. {
  1088.     BitMap    svbits;            /* port's saved bitmap */
  1089.     BitMap    offbits;            /* off screen bitmap */
  1090.     Boolean    reallocated;    /* if true, bitmap needs to be redrawn */
  1091.     long        baseAddrSize;    /* size of base address for bitmap */
  1092.     
  1093.     require(PopupValid(popup));
  1094.     if ( ! (**popup).attr.draw) return;
  1095.     
  1096.     /* setup drawing environment */
  1097.     PopupPortSetup(popup);
  1098.         
  1099.     /* setup offscreen bitmap */
  1100.     if ((**popup).baseAddr) {
  1101.         
  1102.         /* calculate bitmap dimensions */
  1103.         offbits.bounds = (**popup).r.bounds;
  1104.         offbits.rowBytes = ((((offbits.bounds.right - offbits.bounds.left) + 15) / 16) * 2);
  1105.         baseAddrSize = (long) offbits.rowBytes * (offbits.bounds.bottom - offbits.bounds.top);
  1106.  
  1107.         /* reallocate bitmap for drawing offscreen */
  1108.         reallocated = false;
  1109.         if (GetHandleSize((**popup).baseAddr) != baseAddrSize) {
  1110.             ReallocateHandle((**popup).baseAddr, baseAddrSize);
  1111.             reallocated = true;
  1112.         }
  1113.     }
  1114.     
  1115.     /* draw from the offscreen bitmap */
  1116.     if ((**popup).baseAddr && *(**popup).baseAddr && ! MemError()) {
  1117.         
  1118.         /* lock and dereference base address */
  1119.         MoveHHi((**popup).baseAddr);
  1120.         HLock((**popup).baseAddr);
  1121.         offbits.baseAddr = *(**popup).baseAddr;
  1122.     
  1123.         if (reallocated) {
  1124.             /* substitute offscreen bitmap for port's bitmap and draw the popup */
  1125.             GrafPtr port;
  1126.             GetPort(&port);
  1127.             SetPort((**popup).port);
  1128.             svbits = (**popup).port->portBits;
  1129.             SetPortBits(&offbits);
  1130.             PopupDrawDirect(popup);
  1131.             SetPortBits(&svbits);
  1132.             SetPort(port);
  1133.         }
  1134.  
  1135.         /* calculate mask region */
  1136.         if ((**popup).utilRgn) {
  1137.             check(EmptyRgn((**popup).utilRgn));
  1138.             CopyRgn((**popup).port->clipRgn, (**popup).utilRgn);
  1139.             SectRgn((**popup).port->visRgn, (**popup).utilRgn, (**popup).utilRgn);
  1140.         }
  1141.         
  1142.         /* copy the popup from the offscreen bitmap to the popup's port */
  1143.         CopyBits(&offbits, &(**popup).port->portBits,
  1144.             &offbits.bounds, &offbits.bounds, srcCopy, (**popup).utilRgn);
  1145.         
  1146.         if ((**popup).utilRgn)
  1147.             SetEmptyRgn((**popup).utilRgn);
  1148.         HUnlock((**popup).baseAddr);
  1149.     }
  1150.     else {
  1151.         /* couldn't allocate offscreen bitmap, but the popup can still be drawn */
  1152.         PopupDrawDirect(popup);
  1153.     }
  1154.  
  1155.     /* restore drawing environment */
  1156.     PopupPortRestore(popup);
  1157. }
  1158.  
  1159. /* hilite the popup's title; useful for keyboard equivalents of commands */
  1160. void PopupHilite(PopupHandle popup)
  1161. {
  1162.     GrafPtr port;
  1163.     Rect hilite;
  1164.     
  1165.     GetPort(&port);
  1166.     SetPort((**popup).port);
  1167.     hilite = (**popup).r.hilite;
  1168.     InvertRect(&hilite);
  1169.     SetPort(port);
  1170. }
  1171.  
  1172. /*---------------------------------------------------------------------------*/
  1173. /* event handling */
  1174. /*---------------------------------------------------------------------------*/
  1175.  
  1176. /* True if point (in local coordinates) is within the popup menu.
  1177.     Call this before calling PopupMouseDown. */
  1178. Boolean PopupWithin(PopupHandle popup, Point pt)
  1179. {
  1180.     Boolean result = false;
  1181.     
  1182.     require(PopupValid(popup));
  1183.     if ((**popup).attr.visible && (**popup).attr.enabled) {
  1184.         Rect selection = (**popup).r.selection;
  1185.         Rect hilite = (**popup).r.hilite;
  1186.         result = (PtInRect(pt, &selection) || PtInRect(pt, &hilite));
  1187.     }
  1188.     return(result);
  1189. }
  1190.  
  1191. /* call this when there's a mouse down in a popup menu */
  1192. void PopupSelect(PopupHandle popup)
  1193. {
  1194.     long chosen;            /* item selected from menu */
  1195.     Point    location;        /* top left of menu */
  1196.     Boolean inserted;        /* true if inserted menu into menu list */
  1197.     Rect selection;        /* current selection rectangle */
  1198.     short oldMenuWidth;    /* saved menu width */
  1199.     
  1200.     require(PopupValid(popup));
  1201.     if (StillDown()) {
  1202.     
  1203.         /* setup port */
  1204.         PopupPortSetup(popup);
  1205.         
  1206.         /* insert menu into heirarchical menu list if necessary */
  1207.         inserted = false;
  1208.         if (! GetMHandle((**(**popup).menu).menuID)) {
  1209.             InsertMenu((**popup).menu, -1);
  1210.             inserted = true;
  1211.         }
  1212.         
  1213.         /* hilite title */
  1214.         PopupHilite(popup);
  1215.         
  1216.         /* calculate position for popup menu */
  1217.         location.h = (**popup).r.selection.left;
  1218.         location.v = (**popup).r.selection.top;
  1219.         LocalToGlobal(&location);
  1220.                 
  1221.         /* adjust width of menu */
  1222.         oldMenuWidth = (**(**popup).menu).menuWidth;
  1223.         if (! (**popup).attr.typein) {
  1224.             /* make the menu wide enough to include the down arrow */
  1225.             (**(**popup).menu).menuWidth += kArrowWidth + kArrowMargin;
  1226.             if ((**popup).attr.fixedwidth) {
  1227.                 /* make menu fill all of current selection rectangle */
  1228.                 short width = (**popup).r.selection.right - (**popup).r.selection.left;
  1229.                 if (width > (**(**popup).menu).menuWidth)
  1230.                     (**(**popup).menu).menuWidth = width;
  1231.             }
  1232.         }
  1233.         
  1234.         /* let user select an item from the menu */
  1235.         chosen = PopUpMenuSelect((**popup).menu, location.v, location.h,
  1236.                                          (**popup).state.current);
  1237.         
  1238.         /* restore environment */
  1239.         (**(**popup).menu).menuWidth = oldMenuWidth;
  1240.         if (inserted)
  1241.             DeleteMenu((**(**popup).menu).menuID);
  1242.         PopupHilite(popup);
  1243.         PopupPortRestore(popup);
  1244.         
  1245.         /* display the selected item */
  1246.         if (LoWord(chosen))
  1247.             PopupCurrentSet(popup, LoWord(chosen));
  1248.     }
  1249.     ensure(PopupValid(popup));
  1250. }
  1251.     
  1252. /*---------------------------------------------------------------------------*/
  1253. /* getting and setting attributes */
  1254. /*---------------------------------------------------------------------------*/
  1255.  
  1256. /* recalculate and redraw the popup */
  1257. static void PopupChanged(PopupHandle popup)
  1258. {
  1259.     /* purge bitmap to force rebuilding it */
  1260.     if ((**popup).baseAddr)
  1261.         EmptyHandle((**popup).baseAddr);
  1262.  
  1263.     /* recalculate and redraw everything */
  1264.     PopupCalculate(popup);
  1265.     PopupDraw(popup);
  1266. }
  1267.  
  1268. /* return the version of the library that created the popup menu */
  1269. short PopupVersion(PopupHandle popup)
  1270. {
  1271.     return((**popup).version);
  1272. }
  1273.  
  1274. /* return the currently selected menu item */
  1275. short PopupCurrent(PopupHandle popup)
  1276. {
  1277.     require(PopupValid(popup));
  1278.     return((**popup).state.current);
  1279. }
  1280.  
  1281. /* set the currently selected menu item */
  1282. void PopupCurrentSet(PopupHandle popup, short current)
  1283. {
  1284.     require(PopupValid(popup));
  1285.     if (current != (**popup).state.current) {
  1286.         SetItemMark((**popup).menu, (**popup).state.current, noMark);
  1287.         SetItemMark((**popup).menu, current, (**popup).attr.mark);
  1288.         (**popup).state.current = current;
  1289.         PopupChanged(popup);
  1290.     }
  1291. }
  1292.  
  1293. /* turn drawing on or off */
  1294. void PopupDrawSet(PopupHandle popup, Boolean draw)
  1295. {
  1296.     require(PopupValid(popup));
  1297.     (**popup).attr.draw = draw;
  1298. }
  1299.  
  1300. /* make popup visible or invisible */
  1301. void PopupVisibleSet(PopupHandle popup, Boolean visible)
  1302. {    
  1303.     require(PopupValid(popup));    
  1304.     if (visible != (**popup).attr.visible) {
  1305.         (**popup).attr.visible = visible;
  1306.         PopupChanged(popup);
  1307.     }    
  1308. }
  1309.  
  1310. /* set the character used to mark the current menu item */
  1311. void PopupMarkSet(PopupHandle popup, char mark)
  1312. {
  1313.     require(PopupValid(popup));
  1314.     if (mark != (**popup).attr.mark) {
  1315.         (**popup).attr.mark = mark;
  1316.         SetItemMark((**popup).menu, (**popup).state.current, (**popup).attr.mark);
  1317.     }    
  1318. }
  1319.  
  1320. /* enable or disable the menu */
  1321. void PopupEnableSet(PopupHandle popup, Boolean enabled)
  1322. {
  1323.     require(PopupValid(popup));
  1324.     if (enabled != (**popup).attr.enabled) {
  1325.         (**popup).attr.enabled = enabled;
  1326.         PopupChanged(popup);
  1327.     }
  1328. }
  1329.  
  1330. /* turn type-in style menu on or off (IM-VI, p2-37) */
  1331. void PopupTypeInSet(PopupHandle popup, Boolean typein)
  1332. {
  1333.     require(PopupValid(popup));
  1334.     if (typein != (**popup).attr.typein) {
  1335.         (**popup).attr.typein = typein;
  1336.         PopupChanged(popup);
  1337.     }
  1338. }
  1339.  
  1340. /* return rectangle enclosing all of popup */
  1341. void PopupBounds(PopupHandle popup, Rect *bounds)
  1342. {
  1343.     require(PopupValid(popup));
  1344.     *bounds = (**popup).r.bounds;
  1345.     ensure(RectValid(bounds));
  1346. }
  1347.  
  1348. /* set popup's maximum bounding rectangle */
  1349. void PopupBoundsSet(PopupHandle popup, const Rect *maxbounds)
  1350. {
  1351.     Rect oldmax;
  1352.     
  1353.     require(PopupValid(popup));
  1354.     require(RectValid(maxbounds));
  1355.     oldmax = (**popup).r.maxbounds;
  1356.     if (! EqualRect(&oldmax, maxbounds)) {
  1357.         if ((**popup).attr.draw) {
  1358.             PopupPortSetup(popup);
  1359.             PopupErase(popup);
  1360.             PopupPortRestore(popup);
  1361.         }
  1362.         (**popup).r.maxbounds = *maxbounds;
  1363.         PopupChanged(popup);
  1364.     }
  1365. }
  1366.  
  1367. /* return popup's title */
  1368. void PopupTitle(PopupHandle popup, Str255 title)
  1369. {
  1370.     *title = 0;
  1371.     if ((**popup).attr.title.str) {
  1372.         BlockMove(*(**popup).attr.title.str, title,
  1373.             **(**popup).attr.title.str + 1);
  1374.     }
  1375. }
  1376.  
  1377. /* set popup's title  */
  1378. void PopupTitleSet(PopupHandle popup, const Str255 title)
  1379. {
  1380.     Str255 oldtitle;
  1381.     
  1382.     require(PopupValid(popup));
  1383.     if ((**popup).attr.title.str) {
  1384.         PopupTitle(popup, oldtitle);
  1385.         if (! EqualString(title, oldtitle, true, true)) {
  1386.             PtrToXHand(title, (**popup).attr.title.str, *title + 1);
  1387.             PopupChanged(popup);
  1388.         }
  1389.     }
  1390. }
  1391.  
  1392. /* set width of popup's title; the title is resized dynamically
  1393.     if the width is zero */
  1394. void PopupTitleWidthSet(PopupHandle popup, short width)
  1395. {
  1396.     require(width >= 0);
  1397.     if (width != (**popup).attr.title.width) {
  1398.         (**popup).attr.title.width = width;
  1399.         PopupChanged(popup);
  1400.     }
  1401. }
  1402.  
  1403. /* set the text style in which the popup's title will be drawn */
  1404. void PopupTitleStyleSet(PopupHandle popup, Style style)
  1405. {
  1406.     if (style != (**popup).attr.title.style) {
  1407.         (**popup).attr.title.style = style;
  1408.         PopupChanged(popup);
  1409.     }
  1410. }
  1411.  
  1412. /* set whether the popup will use the window's font for drawing the
  1413.     title, current selection, and menu */    
  1414. void PopupUseWFontSet(PopupHandle popup, Boolean wfont)
  1415. {
  1416.     if (wfont != (**popup).attr.wfont) {
  1417.         (**popup).attr.wfont = wfont;
  1418.         PopupChanged(popup);
  1419.     }
  1420. }
  1421.  
  1422. /* set whether the popup will use a fixed width or will be resized
  1423.     dynamically */
  1424. void PopupFixedWidthSet(PopupHandle popup, Boolean fixedwidth)
  1425. {
  1426.     if (fixedwidth != (**popup).attr.fixedwidth) {
  1427.         (**popup).attr.fixedwidth = fixedwidth;
  1428.         PopupChanged(popup);
  1429.     }
  1430. }
  1431.  
  1432. /* set the justification style for drawing the popup */
  1433. void PopupJustSet(PopupHandle popup, short just)
  1434. {
  1435.     if (just != (**popup).attr.just) {
  1436.         (**popup).attr.just = just;
  1437.         PopupChanged(popup);
  1438.     }
  1439. }
  1440.  
  1441. /*---------------------------------------------------------------------------*/
  1442. /* allocation and disposal */
  1443. /*---------------------------------------------------------------------------*/
  1444.  
  1445. /*    Create a popup menu within the rectangle in the specified port.
  1446.     Drawing is initially off. Since the popup's title is initially
  1447.     empty, you should call PopupTitleSet if you want the popup to
  1448.     have a title. When you're finished configuring the popup call
  1449.     PopupDrawSet to enable drawing and then call PopupCalculate.
  1450.     The popup's rectangles are only calculated when drawing
  1451.     is enabled. The popup menu allocates several utility handles,
  1452.     but will function, albeit not as well, even if it can't allocate
  1453.     any of the utility handles. The popup menu is drawn to an offscreen
  1454.     bitmap and then copied to the screen; the storage for the bitmap
  1455.     is kept in a relocatable and purgeable block. */
  1456. PopupHandle PopupBegin(GrafPtr port, MenuHandle menu, const Rect *maxbounds)
  1457. {
  1458.     PopupHandle popup;
  1459.     void *tmp;
  1460.     
  1461.     require(MenuValid(menu));
  1462.     require(RectValid(maxbounds));
  1463.     
  1464.     /* allocate popup */
  1465.     popup = (PopupHandle) NewHandleClear(sizeof(PopupType));
  1466.     if (popup) {
  1467.     
  1468.         /* initialize internal state */
  1469.         (**popup).version = kPopupVersion;
  1470.         (**popup).port = port;
  1471.         (**popup).menu = menu;
  1472.         (**popup).private.mHandle = menu;
  1473.         (**popup).private.mID = (**menu).menuID;
  1474.         (**popup).r.maxbounds = *maxbounds;
  1475.         
  1476.         /* initialize flags affecting display and operation of menu */
  1477.         (**popup).attr.visible = true;
  1478.         (**popup).attr.enabled = true;
  1479.         (**popup).attr.mark = checkMark;
  1480.         (**popup).attr.just = GetSysJust();
  1481.         
  1482.         /* allocate title */
  1483.         tmp = NewHandleClear(1);
  1484.         (**popup).attr.title.str = tmp;
  1485.  
  1486.         /* allocate utility region */
  1487.         tmp = NewRgn();
  1488.         (**popup).utilRgn = tmp;
  1489.  
  1490.         /* allocate region for saving and restoring the clip region */
  1491.         tmp = NewRgn();
  1492.         (**popup).state.oldenv.clip = tmp;
  1493.         
  1494.         /* allocate bitmap's base address; the handle is purgeable since
  1495.             we can always rebuild the data it contains and since we can
  1496.             always draw the popup directly to the screen, though it may
  1497.             flicker a bit */
  1498.         #if ! DONT_DRAW_OFFSCREEN
  1499.             tmp = NewHandle(0);
  1500.             (**popup).baseAddr = tmp;
  1501.             if ((**popup).baseAddr)
  1502.                 HPurge((**popup).baseAddr);
  1503.         #endif /* DONT_DRAW_OFFSCREEN */
  1504.     }
  1505.     ensure(! popup || PopupValid(popup));
  1506.     return(popup);
  1507. }
  1508.  
  1509. /* end use of the popup menu */
  1510. void PopupEnd(PopupHandle popup)
  1511. {
  1512.     require(! popup || PopupValid(popup));
  1513.     if (popup) {
  1514.         if ((**popup).state.oldenv.clip) DisposeRgn((**popup).state.oldenv.clip);
  1515.         if ((**popup).state.selection) DisposeMenu((**popup).state.selection);
  1516.         if ((**popup).attr.title.str) DisposeHandle((**popup).attr.title.str);
  1517.         if ((**popup).baseAddr) DisposeHandle((**popup).baseAddr);
  1518.         if ((**popup).utilRgn) DisposeRgn((**popup).utilRgn);
  1519.         DisposeHandle((Handle) popup);
  1520.         popup = NULL;
  1521.     }
  1522.     ensure(! PopupValid(popup));
  1523. }
  1524.